// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package noder

import (
	"errors"
	"fmt"
	"internal/buildcfg"
	"os"
	pathpkg "path"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"

	"cmd/compile/internal/base"
	"cmd/compile/internal/importer"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/syntax"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/compile/internal/types2"
	"cmd/internal/archive"
	"cmd/internal/bio"
	"cmd/internal/goobj"
	"cmd/internal/objabi"
	"cmd/internal/src"
)

// haveLegacyImports记录我们是否在没有新的导出数据部分的情况下导入了任何包
// 。当您需要支持手动编译带有不一致
// 实验非常有用。
// 编译器标志的文件的现有测试时，这对于使用新的导出数据格式设计进行
var haveLegacyImports = false

// newReadImportFunc是一个扩展钩子，用于试验新的
// 导出数据格式。如果通过重载WriteNeWeExportFunc为导入的包写入了新的导出数据有效载荷
// 则
// 该有效载荷将映射到内存中，并传递给
// newReadImportFunc。
var newReadImportFunc = func(data string, pkg1 *types.Pkg, env *types2.Context, packages map[string]*types2.Package) (pkg2 *types2.Package, err error) {
	panic("unexpected new export data payload")
}

type gcimports struct {
	ctxt     *types2.Context
	packages map[string]*types2.Package
}

func (m *gcimports) Import(path string) (*types2.Package, error) {
	return m.ImportFrom(path, "" /* no vendoring */, 0)
}

func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) {
	if mode != 0 {
		panic("mode must be 0")
	}

	_, pkg, err := readImportFile(path, typecheck.Target, m.ctxt, m.packages)
	return pkg, err
}

func isDriveLetter(b byte) bool {
	return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z'
}

// 此路径是本地名称吗？开始于。/或者或者/
func islocalname(name string) bool {
	return strings.HasPrefix(name, "/") ||
		runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' ||
		strings.HasPrefix(name, "./") || name == "." ||
		strings.HasPrefix(name, "../") || name == ".."
}

func openPackage(path string) (*os.File, error) {
	if islocalname(path) {
		if base.Flag.NoLocalImports {
			return nil, errors.New("local imports disallowed")
		}

		if base.Flag.Cfg.PackageFile != nil {
			return os.Open(base.Flag.Cfg.PackageFile[path])
		}

		// 试试看。a以前。o、 对于构建库很重要：
		// 如果有数组。在阵列中。一个库，
		// 想要找到所有的数组。a、 不仅仅是数组。o、 
		if file, err := os.Open(fmt.Sprintf("%s.a", path)); err == nil {
			return file, nil
		}
		if file, err := os.Open(fmt.Sprintf("%s.o", path)); err == nil {
			return file, nil
		}
		return nil, errors.New("file not found")
	}

	// 本地进口商品应该已经标准化了。
	// 不希望看到“encoding/。/encoding/base64”
	// 与“encoding/base64”不同。
	if q := pathpkg.Clean(path); q != path {
		return nil, fmt.Errorf("non-canonical import path %q (should be %q)", path, q)
	}

	if base.Flag.Cfg.PackageFile != nil {
		return os.Open(base.Flag.Cfg.PackageFile[path])
	}

	for _, dir := range base.Flag.Cfg.ImportDirs {
		if file, err := os.Open(fmt.Sprintf("%s/%s.a", dir, path)); err == nil {
			return file, nil
		}
		if file, err := os.Open(fmt.Sprintf("%s/%s.o", dir, path)); err == nil {
			return file, nil
		}
	}

	if buildcfg.GOROOT != "" {
		suffix := ""
		if base.Flag.InstallSuffix != "" {
			suffix = "_" + base.Flag.InstallSuffix
		} else if base.Flag.Race {
			suffix = "_race"
		} else if base.Flag.MSan {
			suffix = "_msan"
		} else if base.Flag.ASan {
			suffix = "_asan"
		}

		if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil {
			return file, nil
		}
		if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil {
			return file, nil
		}
	}
	return nil, errors.New("file not found")
}

// myheight根据目前导入的包
// 跟踪本地包的高度。
var myheight int

// resolveImportPath将Go中出现的导入路径解析为包的完整路径。
func resolveImportPath(path string) (string, error) {
	// 包名main不再保留，
	// 但是我们保留导入路径“main”来标识
	// 主包，就像我们保留导入
	// 路径“math”来标识标准数学包一样。
	if path == "main" {
		return "", errors.New("cannot import \"main\"")
	}

	if base.Ctxt.Pkgpath != "" && path == base.Ctxt.Pkgpath {
		return "", fmt.Errorf("import %q while compiling that package (import cycle)", path)
	}

	if mapped, ok := base.Flag.Cfg.ImportMap[path]; ok {
		path = mapped
	}

	if islocalname(path) {
		if path[0] == '/' {
			return "", errors.New("import path cannot be absolute path")
		}

		prefix := base.Flag.D
		if prefix == "" {
			// 有问题，但当未指定-D时，历史上我们
			// 解析相对于
			// 编译器当前目录的本地导入路径，而不是相应的源
			// 文件目录。
			prefix = base.Ctxt.Pathname
		}
		path = pathpkg.Join(prefix, path)

		if err := checkImportPath(path, true); err != nil {
			return "", err
		}
	}

	return path, nil
}

func importfile(decl *syntax.ImportDecl) *types.Pkg {
	path, err := parseImportPath(decl.Path)
	if err != nil {
		base.Errorf("%s", err)
		return nil
	}

	pkg, _, err := readImportFile(path, typecheck.Target, nil, nil)
	if err != nil {
		base.Errorf("%s", err)
		return nil
	}

	if pkg != types.UnsafePkg && pkg.Height >= myheight {
		myheight = pkg.Height + 1
	}
	return pkg
}

func parseImportPath(pathLit *syntax.BasicLit) (string, error) {
	if pathLit.Kind != syntax.StringLit {
		return "", errors.New("import path must be a string")
	}

	path, err := strconv.Unquote(pathLit.Value)
	if err != nil {
		return "", errors.New("import path must be a string")
	}

	if err := checkImportPath(path, false); err != nil {
		return "", err
	}

	return path, err
}

// readImportFile读取给定包路径的导入文件，
// 返回其类型。Pkg代表。如果包不为零，则
// types2。包表示也会被返回。
func readImportFile(path string, target *ir.Package, env *types2.Context, packages map[string]*types2.Package) (pkg1 *types.Pkg, pkg2 *types2.Package, err error) {
	path, err = resolveImportPath(path)
	if err != nil {
		return
	}

	if path == "unsafe" {
		pkg1, pkg2 = types.UnsafePkg, types2.Unsafe

		// TODO（mdempsky）：调查这是否真的重要。为什么链接器或运行时会关心导入的包是否不安全？
		if !pkg1.Direct {
			pkg1.Direct = true
			target.Imports = append(target.Imports, pkg1)
		}

		return
	}

	pkg1 = types.NewPkg(path, "")
	if packages != nil {
		pkg2 = packages[path]
		assert(pkg1.Direct == (pkg2 != nil && pkg2.Complete()))
	}

	if pkg1.Direct {
		return
	}
	pkg1.Direct = true
	target.Imports = append(target.Imports, pkg1)

	f, err := openPackage(path)
	if err != nil {
		return
	}
	defer f.Close()

	r, end, newsize, err := findExportData(f)
	if err != nil {
		return
	}

	if base.Debug.Export != 0 {
		fmt.Printf("importing %s (%s)\n", path, f.Name())
	}

	if newsize != 0 {
		// 我们有统一的红外数据。绘制地图，并提供给进口商。
		end -= newsize
		var data string
		data, err = base.MapFile(r.File(), end, newsize)
		if err != nil {
			return
		}

		pkg2, err = newReadImportFunc(data, pkg1, env, packages)
	} else {
		// 我们只有旧数据。好吧，回到传统进口商那里。
		haveLegacyImports = true

		var c byte
		switch c, err = r.ReadByte(); {
		case err != nil:
			return

		case c != 'i':
			// 索引格式以“i”字节区分，而以前的导出格式以“c”、“d”或“v”开头。
			err = fmt.Errorf("unexpected package format byte: %v", c)
			return
		}

		pos := r.Offset()

		// 将字符串（和数据）部分作为单个大的
		// 字符串映射到内存中。这减少了堆碎片，并允许
		// 非常高效地返回单个子字符串。
		var data string
		data, err = base.MapFile(r.File(), pos, end-pos)
		if err != nil {
			return
		}

		typecheck.ReadImports(pkg1, data)

		if packages != nil {
			pkg2, err = importer.ImportData(packages, data, path)
			if err != nil {
				return
			}
		}
	}

	err = addFingerprint(path, f, end)
	return
}

// findExportData返回一个*bio。位于
// 二进制导出数据段开头的读卡器，以及用于停止
// 读取的文件偏移量。
func findExportData(f *os.File) (r *bio.Reader, end, newsize int64, err error) {
	r = bio.NewReader(f)

	// 检查对象头
	line, err := r.ReadString('\n')
	if err != nil {
		return
	}

	if line == "!<arch>\n" { // 包存档
		// 包导出块应该是第一个
		sz := int64(archive.ReadHeader(r.Reader, "__.PKGDEF"))
		if sz <= 0 {
			err = errors.New("not a package file")
			return
		}
		end = r.Offset() + sz
		line, err = r.ReadString('\n')
		if err != nil {
			return
		}
	} else {
		// 不是存档；改为提供文件结尾。
		// TODO（mdempsky）：我认为这种情况不会再发生了。
		var fi os.FileInfo
		fi, err = f.Stat()
		if err != nil {
			return
		}
		end = fi.Size()
	}

	if !strings.HasPrefix(line, "go object ") {
		err = fmt.Errorf("not a go object file: %s", line)
		return
	}
	if expect := objabi.HeaderString(); line != expect {
		err = fmt.Errorf("object is [%s] expected [%s]", line, expect)
		return
	}

	// 进程头行
	for !strings.HasPrefix(line, "$$") {
		if strings.HasPrefix(line, "newexportsize ") {
			fields := strings.Fields(line)
			newsize, err = strconv.ParseInt(fields[1], 10, 64)
			if err != nil {
				return
			}
		}

		line, err = r.ReadString('\n')
		if err != nil {
			return
		}
	}

	// 预计$$B\n将显示二进制导入格式。
	if line != "$$B\n" {
		err = errors.New("old export format no longer supported (recompile library)")
		return
	}

	return
}

// addFingerprint读取包含在
// 导出数据末尾的链接器指纹。
func addFingerprint(path string, f *os.File, end int64) error {
	const eom = "\n$$\n"
	var fingerprint goobj.FingerprintType

	var buf [len(fingerprint) + len(eom)]byte
	if _, err := f.ReadAt(buf[:], end-int64(len(buf))); err != nil {
		return err
	}

	// 调用者应该给我们提供导出数据的结束位置，
	// 应该以“\n$$\n”标记结束。作为一致性检查
	// 为了确保我们的读数在正确的偏移量，请确保我们找到了标记。
	if s := string(buf[len(fingerprint):]); s != eom {
		return fmt.Errorf("expected $$ marker, but found %q", s)
	}

	copy(fingerprint[:], buf[:])

	// 假设文件移动（安装），所以不要记录完整路径
	if base.Flag.Cfg.PackageFile != nil {
		// 如果使用packageFile映射，则假设可以直接记录路径。
		base.Ctxt.AddImport(path, fingerprint)
	} else {
		// 文件“/Users/foo/go/pkg/darwin_amd64/math.a”记录“math.a”。
		file := f.Name()
		base.Ctxt.AddImport(file[len(file)-len(path)-len(".a"):], fingerprint)
	}
	return nil
}

// 链接器使用魔法符号前缀“go”和“类型”
// 暂时拒绝这些保留导入，以避免导入路径和符号之间的潜在混淆。此外，人们
// “可以在GOPATH中做奇怪的事情，我们更希望他们不
// 做那些奇怪的事情”（根据rsc）。另见#4257。
var reservedimports = []string{
	"go",
	"type",
}

func checkImportPath(path string, allowSpace bool) error {
	if path == "" {
		return errors.New("import path is empty")
	}

	if strings.Contains(path, "\x00") {
		return errors.New("import path contains NUL")
	}

	for _, ri := range reservedimports {
		if path == ri {
			return fmt.Errorf("import path %q is reserved and cannot be used", path)
		}
	}

	for _, r := range path {
		switch {
		case r == utf8.RuneError:
			return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path)
		case r < 0x20 || r == 0x7f:
			return fmt.Errorf("import path contains control character: %q", path)
		case r == '\\':
			return fmt.Errorf("import path contains backslash; use slash: %q", path)
		case !allowSpace && unicode.IsSpace(r):
			return fmt.Errorf("import path contains space character: %q", path)
		case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r):
			return fmt.Errorf("import path contains invalid character '%c': %q", r, path)
		}
	}

	return nil
}

func pkgnotused(lineno src.XPos, path string, name string) {
	// 如果导入的包的名称不是最终的
	// 导入路径元素，请在错误消息中显式显示它。
	// 请注意，这将处理重命名的导入和包含非常规包声明的
	// 包的导入。
	// 请注意，即使在Windows上，也会使用/始终，因为Go import 
	// 路径始终使用正斜杠。
	elem := path
	if i := strings.LastIndex(elem, "/"); i >= 0 {
		elem = elem[i+1:]
	}
	if name == "" || elem == name {
		base.ErrorfAt(lineno, "imported and not used: %q", path)
	} else {
		base.ErrorfAt(lineno, "imported and not used: %q as %s", path, name)
	}
}

func mkpackage(pkgname string) {
	if types.LocalPkg.Name == "" {
		if pkgname == "_" {
			base.Errorf("invalid package name _")
		}
		types.LocalPkg.Name = pkgname
	} else {
		if pkgname != types.LocalPkg.Name {
			base.Errorf("package %s; expected %s", pkgname, types.LocalPkg.Name)
		}
	}
}

func clearImports() {
	type importedPkg struct {
		pos  src.XPos
		path string
		name string
	}
	var unused []importedPkg

	for _, s := range types.LocalPkg.Syms {
		n := ir.AsNode(s.Def)
		if n == nil {
			continue
		}
		if n.Op() == ir.OPACK {
			// 扔掉上一个文件中遗留下来的顶级包名
			// 。
			// 如果冲突的顶级名称是由其他文件引入的
			// 错误。
			// 则保留s->block set以导致重新声明
			p := n.(*ir.PkgName)
			if !p.Used && base.SyntaxErrors() == 0 {
				unused = append(unused, importedPkg{p.Pos(), p.Pkg.Path, s.Name})
			}
			s.Def = nil
			continue
		}
		if s.Def != nil && s.Def.Sym() != s {
			// 扔掉上一次导入遗留下来的顶级名称
			// 。“x”
			// 在CheckDotImports中进行类型检查后，我们将报告错误。
			s.Def = nil
			continue
		}
	}

	sort.Slice(unused, func(i, j int) bool { return unused[i].pos.Before(unused[j].pos) })
	for _, pkg := range unused {
		pkgnotused(pkg.pos, pkg.path, pkg.name)
	}
}

// CheckDotImports报告任何未使用的点导入的错误。
func CheckDotImports() {
	for _, pack := range dotImports {
		if !pack.Used {
			base.ErrorfAt(pack.Pos(), "imported and not used: %q", pack.Pkg.Path)
		}
	}

	// 不再需要；释放内存。
	dotImports = nil
	typecheck.DotImportRefs = nil
}

// dotImports跟踪所有已被dot导入的pkgname。
var dotImports []*ir.PkgName

// 在PkgName引用的包中查找所有导出的符号，
// 并在当前包中提供它们
func importDot(pack *ir.PkgName) {
	if typecheck.DotImportRefs == nil {
		typecheck.DotImportRefs = make(map[*ir.Ident]*ir.PkgName)
	}

	opkg := pack.Pkg
	for _, s := range opkg.Syms {
		if s.Def == nil {
			if _, ok := typecheck.DeclImporter[s]; !ok {
				continue
			}
		}
		if !types.IsExported(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7=center dot 
			continue
		}
		s1 := typecheck.Lookup(s.Name)
		if s1.Def != nil {
			pkgerror := fmt.Sprintf("during import %q", opkg.Path)
			typecheck.Redeclared(base.Pos, s1, pkgerror)
			continue
		}

		id := ir.NewIdent(src.NoXPos, s)
		typecheck.DotImportRefs[id] = pack
		s1.Def = id
		s1.Block = 1
	}

	dotImports = append(dotImports, pack)
}

// importName类似于oldname，
// 但如果符号来自另一个包且未导出，则会报告错误。
func importName(sym *types.Sym) ir.Node {
	n := oldname(sym)
	if !types.IsExported(sym.Name) && sym.Pkg != types.LocalPkg {
		n.SetDiag(true)
		base.Errorf("cannot refer to unexported name %s.%s", sym.Pkg.Name, sym.Name)
	}
	return n
}
